Week 12 · Networking and Communication — RFID/NFC

Dec 3, 2025 · Networking RFID NFC

I had some errands to run this week and had to skip class. Between Networking and Communication, I found the latter to be more fascinating. In fact, I had always wanted to explore how RFID/NFC works for a very long time and never quite had the right opportunity. So here we go.

To start with, some basic information I found online and organized by ChatGPT:

What is RFID?

RFID (Radio-Frequency Identification) is a broad family of technologies where tags are read wirelessly by a reader using radio waves. RFID systems exist in several frequency bands:

Many RFID tags are simple and mostly one-way: the reader powers the tag and reads an ID or small amount of data. It is great for fast, large-scale identification.

What is NFC?

NFC (Near Field Communication) is a specific subset of HF RFID that also operates at 13.56 MHz, but is designed for close-range, secure, user-initiated interactions.

Simply put, every NFC system is RFID, but not every RFID system is NFC.

Many years ago I was in Tokyo and I had this friend who at the time worked at NEC Japan, the very company behind the NFC tech for every subway gate. At the time he was very proud and bragged about the advance technology that Tokyo used and how fast those doors can slide open within hundreds of a second and no passenger would need to fear they may be blocked by the bar and got hurt. He briefly mentioned the NFC then and I vaguely remembered the technology that was used. Many years later, I have been to way more cities and countries than I was then and having compared all the subway gates. I guess I had to admit, in terms of gate opening speed, Tokyo is the best in the world and beating all other countries by a lot.

This memory led me to think about how the subway gates work in different cities, especially comparing Boston and Tokyo, since I have used both extensively. After a bit of research, here's a breakdown of how the systems operate in Boston and Tokyo:

Boston vs Tokyo: How the Gates Work

Tokyo (Suica / PASMO)

Boston (MBTA / "the T")

Quick Technical Comparison

Feature Tokyo (Suica / PASMO) Boston (MBTA)
Primary card Suica / PASMO IC cards CharlieCard smart card
Core technology FeliCa (NFC-F, HF RFID) MIFARE-based HF RFID
Phone & watch support Mobile Suica / PASMO, Apple Pay Contactless credit/debit, mobile wallets (AFC 2.0)
User-facing terminology "IC card" (rarely called NFC) "CharlieCard" / "tap-to-pay"
Network coverage Most rail & bus operators across Greater Tokyo MBTA subway & buses; commuter rail/ferry via phased rollout

In summary, both cities are using HF RFID smart cards at their gates. Tokyo's system is built around FeliCa-based NFC cards (Suica/PASMO), while Boston's system historically relied on a MIFARE-based transit card (CharlieCard) and is now adding NFC-based open-loop payments with bank cards and phones.

I referred to a few online resources to gather this information, and one particular site I found really helpful Link Here

Another document I found really helpful is the datasheet of XIAO ESP32S3 Download Here

RFID-RCC522's spec document Here.

RC522 RFID module
Got the RC522 from Anthony. I suppose for this simple task a SAMD21 would do.

Files Download

Arduino / C++ XIAO SAMD21 + RC522 RFID (SPI) UID Reader

#include <SPI.h>
#include <MFRC522.h>

// Your wiring:
const uint8_t SS_PIN  = 6;   // RC522 SDA pin -> XIAO D6 (Chip Select)
const uint8_t RST_PIN = 7;   // RC522 RST pin -> XIAO D7

MFRC522 rfid(SS_PIN, RST_PIN);  // Create MFRC522 instance

void setup() {
  // Start serial for debugging
  Serial.begin(115200);
  while (!Serial) {
    ; // Wait for Serial on SAMD21 (so you can see messages)
  }

  Serial.println("Booting...");
  Serial.println("Initializing SPI and RC522...");

  // Initialize SPI bus (D8=SCK, D9=MISO, D10=MOSI on XIAO SAMD21)
  SPI.begin();

  // Initialize RC522 reader
  rfid.PCD_Init();
  delay(50);

  // Optional: Show reader details
  Serial.print("MFRC522 Firmware version: 0x");
  byte v = rfid.PCD_ReadRegister(MFRC522::VersionReg);
  Serial.println(v, HEX);

  if (v == 0x00 || v == 0xFF) {
    Serial.println("WARNING: Could not communicate with RC522.");
    Serial.println("Check wiring and power (3.3V, GND, SS=D6, RST=D7, MOSI=D10, MISO=D9, SCK=D8).");
  } else {
    Serial.println("RC522 initialized successfully. Present a card to the reader.");
  }
}

void loop() {
  // Look for new cards
  if (!rfid.PICC_IsNewCardPresent()) {
    return; // No new card
  }

  // Select one of the cards
  if (!rfid.PICC_ReadCardSerial()) {
    return; // Read error
  }

  Serial.println("Card detected!");

  // Print UID in HEX
  Serial.print("UID (HEX): ");
  for (byte i = 0; i < rfid.uid.size; i++) {
    if (rfid.uid.uidByte[i] < 0x10) {
      Serial.print("0"); // leading zero
    }
    Serial.print(rfid.uid.uidByte[i], HEX);
    Serial.print(" ");
  }
  Serial.println();

  // Print card type
  MFRC522::PICC_Type piccType = rfid.PICC_GetType(rfid.uid.sak);
  Serial.print("Card type: ");
  Serial.println(rfid.PICC_GetTypeName(piccType));

  Serial.println("------------------------");

  // Halt the card and stop encryption (good practice)
  rfid.PICC_HaltA();
  rfid.PCD_StopCrypto1();

  delay(200);  // small delay so output is readable
}
        
RFID wiring
However in terms of actually soldering this thing. I came to the lab pretty late and they were about to close. Given the relatively simple connection, I improvised a bit and took a few wire just to see if the thing works. I can always come back and solder them together. But frankly speaking, who needs a PCB board when simple wire would do...
Arduino / C++ XIAO SAMD21 + RC522 RFID (Access Control by UID)

#include <SPI.h>
#include <MFRC522.h>

const uint8_t SS_PIN  = 6;   // RC522 SDA -> XIAO D6
const uint8_t RST_PIN = 7;   // RC522 RST -> XIAO D7

MFRC522 rfid(SS_PIN, RST_PIN);

// 👉 Replace this with the UID you want to "allow"
byte allowedUID[] = {0x47, 0x71, 0xB5, 0x11};
byte allowedUIDLength = 4;

bool compareUID(byte* uid, byte uidSize,
                byte* allowed, byte allowedSize) {
  if (uidSize != allowedSize) return false;
  for (byte i = 0; i < uidSize; i++) {
    if (uid[i] != allowed[i]) return false;
  }
  return true;
}

void setup() {
  Serial.begin(115200);
  while (!Serial) {;}

  Serial.println("RFID Access Demo - Present a card.");

  SPI.begin();
  rfid.PCD_Init();
  delay(50);

  Serial.print("Firmware version: 0x");
  Serial.println(
    rfid.PCD_ReadRegister(MFRC522::VersionReg),
    HEX
  );
}

void loop() {
  if (!rfid.PICC_IsNewCardPresent()) return;
  if (!rfid.PICC_ReadCardSerial()) return;

  Serial.println("Card detected!");

  // Print UID
  Serial.print("UID (HEX): ");
  for (byte i = 0; i < rfid.uid.size; i++) {
    if (rfid.uid.uidByte[i] < 0x10)
      Serial.print("0");
    Serial.print(rfid.uid.uidByte[i], HEX);
    Serial.print(" ");
  }
  Serial.println();

  // Check if this is the allowed card
  if (compareUID(
        rfid.uid.uidByte,
        rfid.uid.size,
        allowedUID,
        allowedUIDLength)) {
    Serial.println("✅ Access GRANTED (known card)");
  } else {
    Serial.println("❌ Access DENIED (unknown card)");
  }

  Serial.println("------------------------");

  rfid.PICC_HaltA();
  rfid.PCD_StopCrypto1();

  delay(200);
}
        
Arduino / C++ XIAO SAMD21 + RC522 RFID (MIFARE Classic Write + Verify)

#include <SPI.h>
#include <MFRC522.h>

const uint8_t SS_PIN  = 6;   // RC522 SDA -> XIAO D6
const uint8_t RST_PIN = 7;   // RC522 RST -> XIAO D7

MFRC522 rfid(SS_PIN, RST_PIN);
MFRC522::MIFARE_Key key;

const byte BLOCK_TO_WRITE = 4;  // Sector 1, Block 4 on a MIFARE 1K card

// The string we want to store: "HTMAA 2025 TONY"
byte dataBlock[16] = {
  'H', 'T', 'M', 'A', 'A', ' ', '2', '0',
  '2', '5', ' ', 'T', 'O', 'N', 'Y', 0x00   // last byte = 0x00 padding
};

void printBlock(byte* buffer, byte bufferSize) {
  Serial.print("HEX:   ");
  for (byte i = 0; i < bufferSize; i++) {
    if (buffer[i] < 0x10) Serial.print("0");
    Serial.print(buffer[i], HEX);
    Serial.print(" ");
  }
  Serial.println();

  Serial.print("ASCII: ");
  for (byte i = 0; i < bufferSize; i++) {
    char c = (char)buffer[i];
    if (c >= 32 && c <= 126) {
      Serial.print(c);
    } else {
      Serial.print(".");
    }
  }
  Serial.println();
}

void setup() {
  Serial.begin(115200);
  while (!Serial) {;}

  Serial.println("=== MIFARE Write Demo ===");
  Serial.println("Will write 'HTMAA 2025 TONY' to Block 4 (Sector 1).");
  Serial.println("Present the card to the reader...");

  // Set default key = FF FF FF FF FF FF
  for (byte i = 0; i < 6; i++) {
    key.keyByte[i] = 0xFF;
  }

  SPI.begin();
  rfid.PCD_Init();
  delay(50);

  Serial.print("Firmware version: 0x");
  Serial.println(rfid.PCD_ReadRegister(MFRC522::VersionReg), HEX);
}

void loop() {
  // Wait for a card
  if (!rfid.PICC_IsNewCardPresent()) return;
  if (!rfid.PICC_ReadCardSerial()) return;

  Serial.println("\nNew card detected!");

  // Print UID
  Serial.print("UID: ");
  for (byte i = 0; i < rfid.uid.size; i++) {
    if (rfid.uid.uidByte[i] < 0x10) Serial.print("0");
    Serial.print(rfid.uid.uidByte[i], HEX);
    Serial.print(" ");
  }
  Serial.println();

  MFRC522::PICC_Type piccType = rfid.PICC_GetType(rfid.uid.sak);
  Serial.print("Card type: ");
  Serial.println(rfid.PICC_GetTypeName(piccType));

  // Only proceed for MIFARE Classic types
  if (piccType != MFRC522::PICC_TYPE_MIFARE_1K &&
      piccType != MFRC522::PICC_TYPE_MIFARE_4K &&
      piccType != MFRC522::PICC_TYPE_MIFARE_MINI) {
    Serial.println("This is not a MIFARE Classic-type card. Aborting.");
    rfid.PICC_HaltA();
    rfid.PCD_StopCrypto1();
    return;
  }

  // --- Authenticate the block using Key A ---
  Serial.print("Authenticating block ");
  Serial.print(BLOCK_TO_WRITE);
  Serial.println(" with Key A...");

  MFRC522::StatusCode status;
  status = rfid.PCD_Authenticate(
    MFRC522::PICC_CMD_MF_AUTH_KEY_A,
    BLOCK_TO_WRITE,
    &key,
    &(rfid.uid)
  );

  if (status != MFRC522::STATUS_OK) {
    Serial.print("Authentication failed: ");
    Serial.println(rfid.GetStatusCodeName(status));
    rfid.PICC_HaltA();
    rfid.PCD_StopCrypto1();
    return;
  }

  Serial.println("Authentication success!");

  // --- Write data to the block ---
  Serial.println("Writing data to block...");
  status = rfid.MIFARE_Write(BLOCK_TO_WRITE, dataBlock, 16);
  if (status != MFRC522::STATUS_OK) {
    Serial.print("Write failed: ");
    Serial.println(rfid.GetStatusCodeName(status));
    rfid.PICC_HaltA();
    rfid.PCD_StopCrypto1();
    return;
  }

  Serial.println("Write success!");

  // --- Read back for verification ---
  byte readBuffer[18];
  byte size = sizeof(readBuffer);

  status = rfid.MIFARE_Read(BLOCK_TO_WRITE, readBuffer, &size);
  if (status != MFRC522::STATUS_OK) {
    Serial.print("Read-back failed: ");
    Serial.println(rfid.GetStatusCodeName(status));
  } else {
    Serial.println("Read-back data from block:");
    printBlock(readBuffer, 16);
  }

  // Clean up
  rfid.PICC_HaltA();
  rfid.PCD_StopCrypto1();

  Serial.println("\nRemove card and tap again if you want to rewrite.");
  delay(500);
}
        

Update on Feb 17th, 2026

After finishing the RC522 experiments, I realized this is actually a pretty fun project. I am thinking of doing some more experiments with RFID. My initial thought is to try to copy the very card every MIT student owns -- the MIT student card. Coincidentally, I found this report made about two decades ago by a MIT committee to see how vulnerable the MIT card then was when it faced secuirty/privacy attacks.

The report link: https://groups.csail.mit.edu/mac/classes/6.805/student-papers/fall04-papers/mit_id/#rfid

Also a student's link about how to copy the MIT card in 2023 (we will see if the system had been updated since then to render this method invalid): https://fab.cba.mit.edu/classes/863.23/EECS/people/Yohan/week3/

Week 12 update image
RFID Reader experiment results
RFID hacking tutorials thumbnail
Related video: A whole set of tutorials about RFID hacking